package logg

import (
	"context"
	"errors"
	"fmt"
	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"io"

	"log"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"
)

const UniqueId = "log_id"
const DefaultRequest = "-"

var (
	logger   *zap.SugaredLogger
	mutex    sync.Mutex
	GLogConf Log
)

type Log struct {
	Local       LogLocal
	Kafka       LogKafka
	ErrorWriter io.Writer
	InfoWriter  io.Writer
}
type LogLocal struct {
	Enable     bool             `yaml:"enable"`
	Config     []LogLocalConfig `yaml:"config"`
	Cmd        string           `yaml:"cmd"`
	DateFormat string           `yaml:"date-formt"`
}

type LogLocalConfig struct {
	Path       string `yaml:"path"`
	File       string `yaml:"file"`
	MaxSize    int    `yaml:"max-size"`
	MaxBackups int    `yaml:"max-backups"`
	MaxAge     int    `yaml:"max-age"`
	RotateHour int    `yaml:"rotate-hour"`
	LevelMin   int8   `yaml:"level-min"`
	LevelMax   int8   `yaml:"level-max"`
}

type LogKafka struct {
	Enable   bool     `yaml:"enable"`
	Address  []string `yaml:"address,flow"`
	Topic    string   `yaml:"topic"`
	Level    int8     `yaml:"level"`
	User     string
	Password string
	ConfKey  string `yaml:"confKey"`
}

var (
	GLog Log
)

func init() {
	log.SetFlags(log.Lshortfile | log.Lmicroseconds)
}
func NewLogger(conf Log) error {
	mutex.Lock()
	defer mutex.Unlock()
	if logger != nil {
		return errors.New("logger is inited")
	}

	GLogConf = conf
	var cores []zapcore.Core
	encoderCfg := zapcore.EncoderConfig{
		TimeKey:        "time",
		MessageKey:     "msg",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		StacktraceKey:  "stack",
		EncodeLevel:    zapcore.CapitalLevelEncoder,
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		EncodeDuration: zapcore.StringDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
	}
	encoder := zapcore.NewConsoleEncoder(encoderCfg)

	if conf.Local.Enable {

		for _, c := range conf.Local.Config {
			_, err := os.Stat(c.Path)
			if os.IsNotExist(err) {
				err := os.Mkdir(c.Path, os.ModePerm)
				if err != nil {
					return err
				}
			}

			ll := getWriter(c, conf.Local.DateFormat)

			levelMax := zapcore.Level(c.LevelMax)
			levelMin := zapcore.Level(c.LevelMin)
			if zapcore.InfoLevel >= levelMin && zapcore.InfoLevel <= levelMax {
				GLog.InfoWriter = ll
			}
			if zapcore.WarnLevel >= levelMin && zapcore.WarnLevel <= levelMax {
				GLog.ErrorWriter = ll
			}
			enableFunc := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
				return lev <= levelMax && lev >= levelMin
			})
			cores = append(cores, zapcore.NewCore(encoder, zapcore.AddSync(ll), enableFunc))
		}

	}

	core := zapcore.NewTee(cores...)

	logger = zap.New(
		core,
		zap.AddCaller(),
		zap.AddCallerSkip(1),
		//zap.AddStacktrace(zap.WarnLevel),
	).Sugar()
	return nil
}

func Stop() {
	logger.Sync()
}

func getWriter(conf LogLocalConfig, format string) io.Writer {
	// 生成rotatelogs的Logger 实际生成的文件名 demo.log.YYmmddHH
	// demo.log是指向最新日志的链接
	// 保存7天内的日志，每1小时(整点)分割一次日志
	RotateHour := conf.RotateHour
	if RotateHour == 0 {
		RotateHour = 24
	}

	fname := conf.Path + "/" + conf.File
	hook, err := rotatelogs.New(
		fname+"."+format,
		rotatelogs.WithLinkName(fname),
		rotatelogs.WithMaxAge(time.Hour*24*time.Duration(conf.MaxAge)),     //日志保留n天
		rotatelogs.WithRotationTime(time.Minute*time.Duration(RotateHour)), //n小时创建新日志
	)
	if err != nil {
		panic(err)
	}
	return hook
}

func Debugf(ctx context.Context, msg string, output ...interface{}) {
	msg, stack := logMsg(ctx, zap.DebugLevel, msg, output...)
	if logger != nil {
		logger.Debugw(msg, stack)
	} else {
		log.Print(msg)
	}
}

func Infof(ctx context.Context, msg string, output ...interface{}) {
	msg, stack := logMsg(ctx, zap.InfoLevel, msg, output...)
	if logger != nil {
		logger.Infow(msg, stack)
	} else {
		log.Print(msg)
	}
}

func Warnf(ctx context.Context, msg string, output ...interface{}) {
	msg, stack := logMsg(ctx, zap.WarnLevel, msg, output...)
	if logger != nil {
		logger.Warnw(msg, stack)
	} else {
		log.Print(msg)
	}
}

func Errorf(ctx context.Context, msg string, output ...interface{}) {
	msg, stack := logMsg(ctx, zap.ErrorLevel, msg, output...)
	if logger != nil {
		logger.Errorw(msg, stack)
	} else {
		log.Print(msg)
	}
}

func Fatalf(ctx context.Context, msg string, output ...interface{}) {
	msg, stack := logMsg(ctx, zap.FatalLevel, msg, output...)
	if logger != nil {
		logger.Fatalw(msg, stack)
	} else {
		log.Fatal(msg)
	}
}

func Panicf(ctx context.Context, msg string, output ...interface{}) {
	msg, stack := logMsg(ctx, zap.PanicLevel, msg, output...)
	if logger != nil {
		logger.Panicw(msg, stack)
	} else {
		log.Panic(msg)
	}
}

func Debug(ctx context.Context, output ...interface{}) {
	msg, stack := logMsg(ctx, zap.DebugLevel, "", output...)
	if logger != nil {
		logger.Debugw(msg, stack)
	} else {
		log.Print(msg)
	}
}

func Info(ctx context.Context, output ...interface{}) {
	msg, stack := logMsg(ctx, zap.InfoLevel, "", output...)
	if logger != nil {
		logger.Infow(msg, stack)
	} else {
		log.Print(msg)
	}
}

func Warn(ctx context.Context, output ...interface{}) {
	msg, stack := logMsg(ctx, zap.WarnLevel, "", output...)
	if logger != nil {
		logger.Warnw(msg, stack)
	} else {
		log.Print(msg)
	}
}

func Error(ctx context.Context, output ...interface{}) {
	msg, stack := logMsg(ctx, zap.ErrorLevel, "", output...)
	if logger != nil {
		logger.Errorw(msg, stack)
	} else {
		log.Print(msg)
	}
}

func Fatal(ctx context.Context, output ...interface{}) {
	msg, stack := logMsg(ctx, zap.FatalLevel, "", output...)
	if logger != nil {
		logger.Fatalw(msg, stack)
	} else {
		log.Fatal(msg)
	}
}

func Panic(ctx context.Context, output ...interface{}) {
	msg, stack := logMsg(ctx, zap.PanicLevel, "", output...)
	if logger != nil {
		logger.Panicw(msg, stack)
	} else {
		log.Panic(msg)
	}
}

func logMsg(ctx context.Context, lvl zapcore.Level, msg string, output ...interface{}) (string, zap.Field) {
	if msg == "" && len(output) > 0 {
		msg = fmt.Sprint(output...)
	} else if msg != "" && len(output) > 0 {
		msg = fmt.Sprintf(msg, output...)
	}
	logId := DefaultRequest
	if ctx != nil {
		if id, ok := ctx.Value(UniqueId).(uint32); ok {
			logId = strconv.FormatUint(uint64(id), 10)
		}
	}
	msg = logId + "\t" + msg
	msg = strings.Replace(msg, "\n", "", -1)
	msg = GLogConf.Local.Cmd + "\t" + msg
	if zap.WarnLevel.Enabled(lvl) {
		return msg, zap.Stack("stack")
	}
	return msg + "\t-", zap.Skip()
}
